package com.google.gwt.sample.simplechat;

import javax.swing.*;
import java.applet.Applet;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * The simple chat server for testing Sockets Module for GWT. It is able to send/receive UTF-8 strings
 * followed by a zero byte. Also it sends a crossdomain policy file for Macromedia Flash.
 *
 * @author Aleksey Lagoshin
 */
public class ChatServer extends Applet {

  /**
   * Flash applet sends this request for a crossdomain policy file.
   */
  public static final String POLICY_REQUEST = "<policy-file-request/>";
  /**
   * The crossdomain policy file for Flash. It allows connections to this server on any port.
   */
  public static final String POLICY_RESPONSE = "<cross-domain-policy>\n" +
                                               "  <allow-access-from domain=\"*\" to-ports=\"*\" />\n" +
                                               "</cross-domain-policy>\n";

  /**
   * Thread for listening.
   */
  private Thread listeningThread;

  /**
   * The port for listening.
   */
  private static int port = 777;
  private JTextField tfPort;

  ServerSocket srvSocket;
  private Socket socket;

  private InputStreamReader in;
  private OutputStream out;

  /**
   * The area that displays messages from/to client, simple log panel.
   */
  private JTextArea taMessages = new JTextArea();
  /**
   * The input field for sending messages to client.
   */
  private JTextField tfInput = new JTextField();

  /**
   * Constructs GUI and actions for buttons.
   */
  public void init() {
    setLayout(new BorderLayout());

    taMessages.setEditable(false);
    tfInput.setEnabled(false);
    tfInput.addActionListener(new ActionListener() {
      /**
       * Sends an message from tfInput to client and puts that message to the log panel.
       */
      public void actionPerformed(ActionEvent e) {
        String msg = tfInput.getText();
        tfInput.setText("");

        send(msg);
        println(" >> " + msg);
      }
    });

    // Input field for a port
    tfPort = new JTextField(4);
    tfPort.setText(String.valueOf(port));

    // The "Start" button
    JButton btnStart = new JButton("Start");
    btnStart.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        startListening();
      }
    });

    // Upper panel with port text field and "Start" button
    JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
    panel.add(new Label("Port:"));
    panel.add(tfPort);
    panel.add(btnStart);

    JScrollPane scrollPane;
    scrollPane = new JScrollPane(taMessages);

    add(panel, BorderLayout.NORTH);
    add(scrollPane, BorderLayout.CENTER);
    add(tfInput, BorderLayout.SOUTH);
    setVisible(true);

    taMessages.setText("This is a simple chat server, use an input field above to enter a port\n you want to listen and press \"Start\" button.\n");
  }

  public void destroy() {
    try {
      if (srvSocket != null)
        srvSocket.close();
    }
    catch (IOException e) {
      System.out.println(e.getMessage());
    }
  }

  /**
   * Begins listening to specified port.
   */
  public void startListening() {
    try {
      if (srvSocket != null)
          srvSocket.close();

       if (socket != null)
          socket.close();
      }
      catch (IOException e) {
        println("Exception: " + e.getMessage());
      }

    try {
      port = Integer.parseInt(tfPort.getText());
    }
    catch (NumberFormatException e) {
      println(" ** Incorrect port");
    }

    listeningThread = new Thread(new Runnable() {
      public void run() {
        try {
          srvSocket = new ServerSocket(port);
          println(" ** Listening port " + port + "...");
          while (!listeningThread.isInterrupted()) {
            socket = srvSocket.accept();
            println(" ** Accepted client from IP - " + socket.getInetAddress());
            initSocket();
          }
        }
        catch (IOException e) {
          println("Exception: " + e.getMessage());
        }
      }
    }, "Reading Thread");
    listeningThread.start();
  }

  /**
   * Puts a specified message to the bottom of log panel.
   *
   * @param message a message
   */
  private void println(String message) {
    taMessages.append(message + '\n');
    taMessages.scrollRectToVisible(new Rectangle(0, taMessages.getHeight() + 20, 1, 1));
  }

  /**
   * Gets in/out streams and begins reading data from an input stream.
   */
  public void initSocket() {
    try {
      in = new InputStreamReader(socket.getInputStream(), "UTF-8");
      out = socket.getOutputStream();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
    new Thread(new Runnable() {
      public void run() {
        reading();
      }
    }).start();
  }

  /**
   * Reads data from an input stream, each string terminated by zero byte it puts to log panel.
   */
  private void reading() {
    println("Use \"Enter\" to send a message to client");

    tfInput.setEnabled(true);
    tfInput.requestFocus();

    StringBuffer buffer = new StringBuffer();

    int b;
    try {
      while (socket.isConnected()) {
        b = in.read();
        if (b > 0)
          buffer.append(new Character((char) b));
          // End of stream
        else if (b == -1)
          break;
          // If a zero byte is received
        else {
          // Put a received message to the log panel
          println(" << " + buffer.toString());
          // If a received message is a request for crossdomain policy file...
          if (buffer.toString().equals(POLICY_REQUEST)) {
            println(POLICY_RESPONSE);
            // Sending the crossdomain policy file
            send(POLICY_RESPONSE);
            return;
          }
          // Clear the buffer
          buffer.setLength(0);
        }
      }
    }
    catch (IOException e) {
      e.printStackTrace();
    }
    finally {
      println(" ** Client has been disconnected");
      tfInput.setEnabled(false);
    }
  }

  /**
   * Sends an UTF-8 message followed by a zero byte to client.
   *
   * @param message entered message
   */
  public void send(String message) {
    try {
      out.write(message.getBytes("UTF-8"));
      out.write(0);
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

}